 /*******************************************************************************
  * Copyright (c) 2000, 2006 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.ui.externaltools.internal.ui;


 import java.lang.reflect.InvocationTargetException ;
 import java.util.ArrayList ;
 import java.util.HashMap ;
 import java.util.Iterator ;
 import java.util.List ;
 import java.util.Map ;

 import org.eclipse.core.resources.ICommand;
 import org.eclipse.core.resources.IFolder;
 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.resources.IProjectDescription;
 import org.eclipse.core.resources.IResource;
 import org.eclipse.core.resources.IWorkspace;
 import org.eclipse.core.resources.IWorkspaceDescription;
 import org.eclipse.core.resources.IncrementalProjectBuilder;
 import org.eclipse.core.resources.ResourcesPlugin;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IAdaptable;
 import org.eclipse.core.runtime.IExtension;
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Platform;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.debug.core.DebugPlugin;
 import org.eclipse.debug.core.ILaunchConfiguration;
 import org.eclipse.debug.core.ILaunchConfigurationListener;
 import org.eclipse.debug.core.ILaunchConfigurationType;
 import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
 import org.eclipse.debug.core.ILaunchManager;
 import org.eclipse.debug.ui.DebugUITools;
 import org.eclipse.jface.dialogs.ErrorDialog;
 import org.eclipse.jface.dialogs.IDialogConstants;
 import org.eclipse.jface.dialogs.MessageDialog;
 import org.eclipse.jface.dialogs.MessageDialogWithToggle;
 import org.eclipse.jface.operation.IRunnableWithProgress;
 import org.eclipse.jface.preference.IPreferenceStore;
 import org.eclipse.jface.viewers.CheckStateChangedEvent;
 import org.eclipse.jface.viewers.CheckboxTableViewer;
 import org.eclipse.jface.viewers.ICheckStateListener;
 import org.eclipse.jface.viewers.ILabelProvider;
 import org.eclipse.jface.viewers.IStructuredSelection;
 import org.eclipse.jface.window.Window;
 import org.eclipse.osgi.util.NLS;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.events.SelectionAdapter;
 import org.eclipse.swt.events.SelectionEvent;
 import org.eclipse.swt.events.SelectionListener;
 import org.eclipse.swt.graphics.Font;
 import org.eclipse.swt.layout.GridData;
 import org.eclipse.swt.layout.GridLayout;
 import org.eclipse.swt.widgets.Button;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Control;
 import org.eclipse.swt.widgets.Display;
 import org.eclipse.swt.widgets.Event;
 import org.eclipse.swt.widgets.Label;
 import org.eclipse.swt.widgets.Listener;
 import org.eclipse.swt.widgets.Shell;
 import org.eclipse.swt.widgets.Table;
 import org.eclipse.swt.widgets.TableItem;
 import org.eclipse.ui.PlatformUI;
 import org.eclipse.ui.dialogs.ElementListSelectionDialog;
 import org.eclipse.ui.dialogs.PropertyPage;
 import org.eclipse.ui.externaltools.internal.launchConfigurations.ExternalToolsMainTab;
 import org.eclipse.ui.externaltools.internal.launchConfigurations.ExternalToolsUtil;
 import org.eclipse.ui.externaltools.internal.launchConfigurations.IgnoreWhiteSpaceComparator;
 import org.eclipse.ui.externaltools.internal.model.BuilderUtils;
 import org.eclipse.ui.externaltools.internal.model.ExternalToolBuilder;
 import org.eclipse.ui.externaltools.internal.model.ExternalToolsPlugin;
 import org.eclipse.ui.externaltools.internal.model.IExternalToolConstants;
 import org.eclipse.ui.externaltools.internal.model.IExternalToolsHelpContextIds;
 import org.eclipse.ui.externaltools.internal.model.IPreferenceConstants;
 import org.eclipse.ui.progress.IProgressService;

 /**
  * Property page to add external tools builders.
  */
 public final class BuilderPropertyPage extends PropertyPage implements ICheckStateListener {

     //locally mark a command's enabled state so it can be processed correctly on performOK
 private static final String COMMAND_ENABLED= "CommandEnabled"; //$NON-NLS-1$

     private Button upButton, downButton, newButton, importButton, editButton, removeButton;
     
     private boolean userHasMadeChanges= false;
     
     private List configsToBeDeleted= null;
     private List commandsToBeDeleted= null;
     
     private CheckboxTableViewer viewer= null;
     
     private ILabelProvider labelProvider= new BuilderLabelProvider();
     
     /**
      * Error configs are objects representing entries pointing to
      * invalid launch configurations
      */
     public class ErrorConfig {
         private ICommand command;
         public ErrorConfig(ICommand command) {
             this.command= command;
         }
         public ICommand getCommand() {
             return command;
         }
     }
     
     /**
      * Collection of configurations created while the page is open.
      * Stored here so they can be deleted if the page is cancelled.
      */
     private List newConfigList= new ArrayList ();
     
     private SelectionListener buttonListener= new SelectionAdapter() {
         public void widgetSelected(SelectionEvent e) {
             handleButtonPressed((Button) e.widget);
         }
     };
     
     /**
      * Launch configuration listener which is responsible for updating items in
      * the tree when the user renames configurations in the dialog.
      *
      * This is necessary because when we tell the configuration dialog to open
      * on a launch config and the user renames that config, the old config (the
      * one in the tree) is made obsolete and a new config is created. This
      * listener hears when new configurations are created this way and replaces
      * the old configuration with the new.
      */
     private ILaunchConfigurationListener configurationListener= new ILaunchConfigurationListener() {
         /**
          * A launch configuration has been added. If this config has been
          * movedFrom a configuration in the tree, replace the old config with
          * the new.
          */
         public void launchConfigurationAdded(final ILaunchConfiguration configuration) {
             ILaunchManager manager= DebugPlugin.getDefault().getLaunchManager();
             final ILaunchConfiguration oldConfig= manager.getMovedFrom(configuration);
             if (oldConfig == null) {
                 return;
             }
             //Replace the movedFrom config in the list of newly created configs
 if (newConfigList.remove(oldConfig)) {
                 newConfigList.add(configuration);
             }
             
             Display.getDefault().asyncExec(new Runnable () {
                 public void run() {
                     TableItem[] items= viewer.getTable().getItems();
                     for (int i = 0; i < items.length; i++) {
                         TableItem item = items[i];
                         Object data= item.getData();
                         if (data == oldConfig) {
                             // Found the movedFrom config in the tree. Replace it with the new config
 item.setData(configuration);
                             viewer.update(configuration, null);
                             break;
                         }
                     }
                 }
             });
         }
         public void launchConfigurationChanged(ILaunchConfiguration configuration) {
         }
         public void launchConfigurationRemoved(ILaunchConfiguration configuration) {
         }
     };

     /**
      * Creates an initialized property page
      */
     public BuilderPropertyPage() {
         super();
         noDefaultAndApplyButton();
     }

     /**
      * Add the project's build to the table viewer.
      */
     private void addBuildersToTable() {
         IProject project = getInputProject();
         if (project == null) {
             return;
         }
         //add build spec entries to the table
 ICommand[] commands= null;
         try {
             commands = project.getDescription().getBuildSpec();
         } catch (CoreException e) {
             handleException(e);
             return;
         }
     
         boolean projectNeedsMigration= false;
         for (int i = 0; i < commands.length; i++) {
             String [] version= new String [] {""}; //$NON-NLS-1$
 ILaunchConfiguration config = BuilderUtils.configFromBuildCommandArgs(project, commands[i].getArguments(), version);
             if (BuilderUtils.VERSION_2_1.equals(version[0])) {
                 // Storing the .project file of a project with 2.1 configs, will
 // edit the file in a way that isn't backwards compatible.
 projectNeedsMigration= true;
             }
             Object element= null;
             if (config != null) {
                 if (!config.isWorkingCopy() && !config.exists()) {
                     Shell shell= getShell();
                     if (shell == null) {
                         return;
                     }
                     IStatus status = new Status(IStatus.ERROR, IExternalToolConstants.PLUGIN_ID, 0, NLS.bind(ExternalToolsUIMessages.BuilderPropertyPage_Exists, new String []{config.getLocation().toOSString()}), null);
                     ErrorDialog.openError(getShell(), ExternalToolsUIMessages.BuilderPropertyPage_errorTitle,
                                     NLS.bind(ExternalToolsUIMessages.BuilderPropertyPage_External_Tool_Builder__0__Not_Added_2, new String []{config.getName()}),
                                     status);
                     userHasMadeChanges= true;
                 } else {
                     element= config;
                 }
             } else {
                 String builderID = commands[i].getBuilderName();
                 if (builderID.equals(ExternalToolBuilder.ID) && commands[i].getArguments().get(BuilderUtils.LAUNCH_CONFIG_HANDLE) != null) {
                     // An invalid external tool entry.
 element= new ErrorConfig(commands[i]);
                 } else {
                     element= commands[i];
                 }
             }
             if (element != null) {
                 viewer.add(element);
                 viewer.setChecked(element, isEnabled(element));
             }
         }
         if (projectNeedsMigration) {
             IPreferenceStore store= ExternalToolsPlugin.getDefault().getPreferenceStore();
             boolean prompt= store.getBoolean(IPreferenceConstants.PROMPT_FOR_PROJECT_MIGRATION);
             boolean proceed= true;
             if (prompt) {
                 Shell shell= getShell();
                 if (shell == null) {
                     return;
                 }
                 MessageDialogWithToggle dialog= MessageDialogWithToggle.openYesNoQuestion(shell, ExternalToolsUIMessages.BuilderPropertyPage_0, ExternalToolsUIMessages.BuilderPropertyPage_1, ExternalToolsUIMessages.BuilderPropertyPage_2, false, null, null);
                 proceed= dialog.getReturnCode() == IDialogConstants.YES_ID;
                 store.setValue(IPreferenceConstants.PROMPT_FOR_PROJECT_MIGRATION, !dialog.getToggleState());
             }
             if (!proceed) {
                 // Open the page read-only
 viewer.getTable().setEnabled(false);
                 downButton.setEnabled(false);
                 editButton.setEnabled(false);
                 importButton.setEnabled(false);
                 newButton.setEnabled(false);
                 removeButton.setEnabled(false);
             }
         }
     }

     /**
      * Creates and returns a button with the given label, id, and enablement.
      */
     private Button createButton(Composite parent, String label) {
         Button button = new Button(parent, SWT.PUSH);
         GridData data = new GridData();
         data.widthHint = convertHorizontalDLUsToPixels(IDialogConstants.BUTTON_WIDTH);
         button.setLayoutData(data);
         button.setFont(parent.getFont());
         button.setText(label);
         button.setEnabled(false);
         button.addSelectionListener(buttonListener);
         return button;
     }

     /* (non-Javadoc)
      * Method declared on PreferencePage.
      */
     protected Control createContents(Composite parent) {
         
         PlatformUI.getWorkbench().getHelpSystem().setHelp(parent, IExternalToolsHelpContextIds.EXTERNAL_TOOLS_BUILDER_PROPERTY_PAGE);
         
         Font font = parent.getFont();

         Composite topLevel = new Composite(parent, SWT.NONE);
         GridLayout layout = new GridLayout();
         layout.marginHeight = 0;
         layout.marginWidth = 0;
         topLevel.setLayout(layout);
         topLevel.setLayoutData(new GridData(GridData.FILL_BOTH));

         Label description = new Label(topLevel, SWT.WRAP);
         description.setText(ExternalToolsUIMessages.BuilderPropertyPage_description);
         description.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
         description.setFont(font);

         Composite tableAndButtons = new Composite(topLevel, SWT.NONE);
         tableAndButtons.setLayoutData(new GridData(GridData.FILL_BOTH));
         layout = new GridLayout();
         layout.marginHeight = 0;
         layout.marginWidth = 0;
         layout.numColumns = 2;
         tableAndButtons.setLayout(layout);

         // table of builders and tools
 viewer= CheckboxTableViewer.newCheckList(tableAndButtons, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION | SWT.BORDER);
         viewer.setLabelProvider(labelProvider);
         viewer.addCheckStateListener(this);
         Table builderTable= viewer.getTable();
         builderTable.setLayoutData(new GridData(GridData.FILL_BOTH));
         builderTable.setFont(font);
         builderTable.addSelectionListener(new SelectionAdapter() {
             public void widgetSelected(SelectionEvent e) {
                 handleTableSelectionChanged();
             }
         });
         
         builderTable.addListener(SWT.MouseDoubleClick, new Listener() {
             public void handleEvent(Event event) {
                 handleEditButtonPressed();
             }
         });
         
         //button area
 Composite buttonArea = new Composite(tableAndButtons, SWT.NONE);
         layout = new GridLayout();
         layout.marginHeight = 0;
         layout.marginWidth = 0;
         buttonArea.setLayout(layout);
         buttonArea.setFont(font);
         buttonArea.setLayoutData(new GridData(GridData.FILL_VERTICAL));
         newButton = createButton(buttonArea, ExternalToolsUIMessages.BuilderPropertyPage_newButton);
         importButton = createButton(buttonArea, ExternalToolsUIMessages.BuilderPropertyPage__Import____3);
         editButton = createButton(buttonArea, ExternalToolsUIMessages.BuilderPropertyPage_editButton);
         removeButton = createButton(buttonArea, ExternalToolsUIMessages.BuilderPropertyPage_removeButton);
         new Label(buttonArea, SWT.LEFT);
         upButton = createButton(buttonArea, ExternalToolsUIMessages.BuilderPropertyPage_upButton);
         downButton = createButton(buttonArea, ExternalToolsUIMessages.BuilderPropertyPage_downButton);

         newButton.setEnabled(true);
         importButton.setEnabled(true);

         //populate widget contents
 addBuildersToTable();

         return topLevel;
     }
     
     /**
      * Turns auto-building on or off in the workspace.
      */
     private void setAutobuild(boolean newState) throws CoreException {
         IWorkspace workspace= ResourcesPlugin.getWorkspace();
         IWorkspaceDescription wsDescription= workspace.getDescription();
         boolean oldState= wsDescription.isAutoBuilding();
         if (oldState != newState) {
             wsDescription.setAutoBuilding(newState);
             workspace.setDescription(wsDescription);
         }
     }

     /**
      * Returns the project that is the input for this property page,
      * or <code>null</code>.
      */
     private IProject getInputProject() {
         IAdaptable element = getElement();
         if (element instanceof IProject) {
             return (IProject) element;
         }
         Object resource = element.getAdapter(IResource.class);
         if (resource instanceof IProject) {
             return (IProject) resource;
         }
         return null;
     }

     /**
      * One of the buttons has been pressed, act accordingly.
      */
     private void handleButtonPressed(Button button) {
         if (button == newButton) {
             handleNewButtonPressed();
         } else if (button == importButton) {
             handleImportButtonPressed();
         } else if (button == editButton) {
             handleEditButtonPressed();
         } else if (button == removeButton) {
             handleRemoveButtonPressed();
         } else if (button == upButton) {
             moveSelectionUp();
         } else if (button == downButton) {
             moveSelectionDown();
         }
         if (getControl().isDisposed()) {
             return;
         }
         handleTableSelectionChanged();
         viewer.getTable().setFocus();
     }

     /* (non-Javadoc)
      * @see org.eclipse.jface.viewers.ICheckStateListener#checkStateChanged(org.eclipse.jface.viewers.CheckStateChangedEvent)
      */
     public void checkStateChanged(CheckStateChangedEvent event) {
         Object element= event.getElement();
         boolean checked = event.getChecked();
         if (element instanceof ILaunchConfiguration) {
             enableLaunchConfiguration((ILaunchConfiguration) element, checked);
         } else if (element instanceof ICommand) {
             Shell shell= getShell();
             if (shell == null) {
                 return;
             }
             if (checked) {
                 enableCommand((ICommand)element, checked);
             } else if (MessageDialog.openConfirm(shell, ExternalToolsUIMessages.BuilderPropertyPage_6, ExternalToolsUIMessages.BuilderPropertyPage_7)) {
                 enableCommand((ICommand)element, checked);
             } else {
                 viewer.removeCheckStateListener(this);
                 viewer.setChecked(element, true);
                 viewer.addCheckStateListener(this);
             }
         }
     }

     private void enableLaunchConfiguration(ILaunchConfiguration configuration, boolean enable) {
         ILaunchConfigurationWorkingCopy workingCopy= null;
         try {
             if (configuration instanceof ILaunchConfigurationWorkingCopy) {
                 workingCopy = (ILaunchConfigurationWorkingCopy) configuration;
             } else {
                 // Replace the config with a working copy
 TableItem[] items= viewer.getTable().getItems();
                 for (int i = 0; i < items.length; i++) {
                     TableItem item = items[i];
                     if (item.getData() == configuration) {
                         workingCopy = configuration.getWorkingCopy();
                         item.setData(workingCopy);
                     }
                 }
             }
             if (workingCopy != null) {
                 workingCopy.setAttribute(IExternalToolConstants.ATTR_BUILDER_ENABLED, enable);
             }
         } catch (CoreException e) {
             return;
         }
         userHasMadeChanges= true;
     }
     
     private void enableCommand(ICommand command, boolean enable) {
         Map args= command.getArguments();
         if (args == null) {
             args= new HashMap (1);
         }
         args.put(COMMAND_ENABLED, Boolean.valueOf(enable));
         command.setArguments(args);
         userHasMadeChanges= true;
     }

     /**
      * The user has pressed the import button. Prompt them to select a
      * configuration to import from the workspace.
      */
     private void handleImportButtonPressed() {
         ILaunchManager manager= DebugPlugin.getDefault().getLaunchManager();
         List toolTypes= getConfigurationTypes(IExternalToolConstants.ID_EXTERNAL_TOOLS_LAUNCH_CATEGORY);
         List configurations= new ArrayList ();
         Iterator iter= toolTypes.iterator();
         while (iter.hasNext()) {
             try {
                 ILaunchConfiguration[] configs= manager.getLaunchConfigurations((ILaunchConfigurationType) iter.next());
                 for (int i = 0; i < configs.length; i++) {
                     ILaunchConfiguration launchConfiguration = configs[i];
                     if (!DebugUITools.isPrivate(launchConfiguration)) {
                         configurations.add(launchConfiguration);
                     }
                 }
             } catch (CoreException e) {
             }
         }
         Shell shell= getShell();
         if (shell == null) {
             return;
         }
         ElementListSelectionDialog dialog= new ElementListSelectionDialog(shell, new BuilderLabelProvider());
         dialog.setTitle(ExternalToolsUIMessages.BuilderPropertyPage_4);
         dialog.setMessage(ExternalToolsUIMessages.BuilderPropertyPage_5);
         dialog.setElements(configurations.toArray());
         if (dialog.open() == Window.CANCEL) {
             return;
         }
         Object results[]= dialog.getResult();
         if (results.length == 0) { //OK pressed with nothing selected
 return;
         }
         ILaunchConfiguration config= (ILaunchConfiguration) results[0];
         ILaunchConfiguration newConfig= null;
         boolean wasAutobuilding= ResourcesPlugin.getWorkspace().getDescription().isAutoBuilding();
         try {
             setAutobuild(false);
             newConfig= BuilderUtils.duplicateConfiguration(getInputProject(), config);
         } catch (CoreException e) {
             handleException(e);
         } finally {
             try {
                 setAutobuild(wasAutobuilding);
             } catch (CoreException e) {
                 handleException(e);
             }
         }
         if (newConfig != null) {
             userHasMadeChanges= true;
             viewer.add(newConfig);
             viewer.setChecked(newConfig, isEnabled(newConfig));
             newConfigList.add(newConfig);
         }
     }
     
     /**
      * The user has pressed the remove button. Delete the selected builder.
      */
     private void handleRemoveButtonPressed() {
         IStructuredSelection selection = (IStructuredSelection) viewer.getSelection();
         if (selection != null) {
             int numSelected= selection.size();
             userHasMadeChanges= true;
             Iterator iterator= selection.iterator();
             while (iterator.hasNext()) {
                 Object item= iterator.next();
                 if (item instanceof ILaunchConfiguration) {
                     if (configsToBeDeleted == null) {
                         configsToBeDeleted= new ArrayList (numSelected);
                     }
                     configsToBeDeleted.add(item);
                 } else if (item instanceof ICommand) {
                     if (commandsToBeDeleted == null) {
                         commandsToBeDeleted= new ArrayList (numSelected);
                     }
                     commandsToBeDeleted.add(item);
                 }
                 viewer.remove(item);
             }
         }
     }
     
     /**
      * The user has pressed the new button. Create a new configuration and open
      * the launch configuration edit dialog on the new config.
      */
     private void handleNewButtonPressed() {
         ILaunchConfigurationType type = promptForConfigurationType();
         if (type == null) {
             return;
         }
         boolean wasAutobuilding= ResourcesPlugin.getWorkspace().getDescription().isAutoBuilding();
         try {
             ILaunchConfigurationWorkingCopy workingCopy = null;
             String name= DebugPlugin.getDefault().getLaunchManager().generateUniqueLaunchConfigurationNameFrom(ExternalToolsUIMessages.BuilderPropertyPage_New_Builder_7);
             workingCopy = type.newInstance(BuilderUtils.getBuilderFolder(getInputProject(), true), name);
             
             StringBuffer buffer= new StringBuffer (IExternalToolConstants.BUILD_TYPE_FULL);
             buffer.append(',');
             buffer.append(IExternalToolConstants.BUILD_TYPE_INCREMENTAL);
             buffer.append(',');
             workingCopy.setAttribute(IExternalToolConstants.ATTR_RUN_BUILD_KINDS, buffer.toString());
             workingCopy.setAttribute(ExternalToolsMainTab.FIRST_EDIT, true);
             ILaunchConfiguration config = null;
             setAutobuild(false);
             config = workingCopy.doSave();
             //needs to be added here in case the user hits apply in the edit dialog
 //then we can correctly update the list with the new config.
 newConfigList.add(config);
             int code= editConfiguration(config);
             if (code == Window.CANCEL) {
                 // If the user cancelled, delete the newly created config
 newConfigList.remove(config);
                 config.delete();
             } else {
                 userHasMadeChanges= true;
                 //retrieve the last "new" config
 //may have been changed by the user pressing apply in the edit dialog
 config= (ILaunchConfiguration)newConfigList.get(newConfigList.size() - 1);
                 viewer.add(config);
                 viewer.setChecked(config, isEnabled(config));
             }
         } catch (CoreException e) {
             handleException(e);
         } finally {
             try {
                 setAutobuild(wasAutobuilding);
             } catch (CoreException e) {
                 handleException(e);
             }
         }
     }
     
     /**
      * Prompts the user to edit the given launch configuration. Returns the
      * return code from opening the launch configuration dialog.
      */
     private int editConfiguration(ILaunchConfiguration config) {
         ILaunchManager manager= DebugPlugin.getDefault().getLaunchManager();
         manager.addLaunchConfigurationListener(configurationListener);
         Shell shell= getShell();
         if (shell == null) {
             return Window.CANCEL;
         }
         int code= DebugUITools.openLaunchConfigurationPropertiesDialog(shell, config, IExternalToolConstants.ID_EXTERNAL_TOOLS_BUILDER_LAUNCH_GROUP);
         manager.removeLaunchConfigurationListener(configurationListener);
         return code;
     }
     
     /**
      * Prompts the user to choose a launch configuration type to create and
      * returns the type the user selected or <code>null</code> if the user
      * cancelled.
      *
      * @return the configuration type selected by the user or <code>null</code>
      * if the user cancelled.
      */
     private ILaunchConfigurationType promptForConfigurationType() {
         List externalToolTypes= getConfigurationTypes(IExternalToolConstants.ID_EXTERNAL_TOOLS_BUILDER_LAUNCH_CATEGORY);
         Shell shell= getShell();
         if (shell == null) {
             return null;
         }
         ElementListSelectionDialog dialog = new ElementListSelectionDialog(shell, new BuilderLabelProvider());
         dialog.setElements(externalToolTypes.toArray());
         dialog.setMultipleSelection(false);
         dialog.setTitle(ExternalToolsUIMessages.BuilderPropertyPage_Choose_configuration_type_8);
         dialog.setMessage(ExternalToolsUIMessages.BuilderPropertyPage_Choose_an_external_tool_type_to_create_9);
         dialog.open();
         Object result[] = dialog.getResult();
         if (result == null || result.length == 0) {
             return null;
         }
         return (ILaunchConfigurationType) result[0];
     }
     
     /**
      * Returns the launch configuration types of the given category
      */
     private List getConfigurationTypes(String category) {
         ILaunchConfigurationType types[] = DebugPlugin.getDefault().getLaunchManager().getLaunchConfigurationTypes();
         List externalToolTypes = new ArrayList ();
         for (int i = 0; i < types.length; i++) {
             ILaunchConfigurationType configurationType = types[i];
             if (category.equals(configurationType.getCategory())) {
                 externalToolTypes.add(configurationType);
             }
         }
         return externalToolTypes;
     }
     
     /**
      * The user has pressed the edit button or double-clicked. Open the launch configuration edit
      * dialog on the selection after migrating the tool if necessary.
      */
     private void handleEditButtonPressed() {
         TableItem[] items= viewer.getTable().getSelection();
         if (items.length == 0) {
             return;
         }
         TableItem selection= items[0];
         if (selection != null) {
             Object data = selection.getData();
             if (data instanceof ILaunchConfiguration) {
                 ILaunchConfiguration config= (ILaunchConfiguration) data;
                 if (BuilderUtils.isUnmigratedConfig(config)) {
                     if (!shouldProceedWithMigration()) {
                         return;
                     }
                     try {
                         config= BuilderUtils.migrateBuilderConfiguration(getInputProject(), (ILaunchConfigurationWorkingCopy) config);
                     } catch (CoreException e) {
                         handleException(e);
                         return;
                     }
                     // Replace the working copy in the table with the migrated configuration
 selection.setData(config);
                 }
                 userHasMadeChanges= true;
                 boolean wasAutobuilding= ResourcesPlugin.getWorkspace().getDescription().isAutoBuilding();
                 try {
                     setAutobuild(false);
                     editConfiguration(config);
                 } catch (CoreException e) {
                     handleException(e);
                 } finally {
                     try {
                         setAutobuild(wasAutobuilding);
                     } catch (CoreException e) {
                         handleException(e);
                     }
                 }
             } else if (data instanceof ICommand) {
                 ICommand command= (ICommand) data;
                 if (command.isConfigurable()) {
                     if (editCommand(command)) {
                         userHasMadeChanges= true;
                     }
                 }
             }
         }
     }
     
     private boolean editCommand(ICommand data) {
         EditCommandDialog dialog= new EditCommandDialog(getShell(), data);
         return Window.OK == dialog.open();
     }

     /**
      * Prompts the user to proceed with the migration of a project builder from
      * the old format to the new, launch configuration-based, format and returns
      * whether or not the user wishes to proceed with the migration.
      *
      * @return boolean whether or not the user wishes to proceed with migration
      */
     private boolean shouldProceedWithMigration() {
         if (!ExternalToolsPlugin.getDefault().getPreferenceStore().getBoolean(IPreferenceConstants.PROMPT_FOR_TOOL_MIGRATION)) {
             // User has asked not to be prompted
 return true;
         }
         Shell shell= getShell();
         if (shell == null) {
             return false;
         }
         // Warn the user that editing an old config will cause storage migration.
 MessageDialogWithToggle dialog= MessageDialogWithToggle.openYesNoQuestion(getShell(),
             ExternalToolsUIMessages.BuilderPropertyPage_Migrate_project_builder_10,
             ExternalToolsUIMessages.BuilderPropertyPage_Not_Support,
             ExternalToolsUIMessages.BuilderPropertyPage_Prompt,
             false,
             ExternalToolsPlugin.getDefault().getPreferenceStore(),
             IPreferenceConstants.PROMPT_FOR_TOOL_MIGRATION);
         return dialog.getReturnCode() == IDialogConstants.YES_ID;
     }

     /**
      * Handles unexpected internal exceptions
      */
     private void handleException(Exception e) {
         final IStatus[] status= new IStatus[1];
         if (e instanceof CoreException) {
             status[0] = ((CoreException) e).getStatus();
         } else {
             status[0] = new Status(IStatus.ERROR, IExternalToolConstants.PLUGIN_ID, 0, ExternalToolsUIMessages.BuilderPropertyPage_statusMessage, e);
         }
         Display.getDefault().asyncExec(new Runnable () {
             public void run() {
                 Shell shell= getShell();
                 if (shell != null) {
                     ErrorDialog.openError(shell, ExternalToolsUIMessages.BuilderPropertyPage_errorTitle,
                             ExternalToolsUIMessages.BuilderPropertyPage_errorMessage,
                             status[0]);
                 }
             }
         });
     }

     /**
      * The user has selected a different builder in table.
      * Update button enablement.
      */
     private void handleTableSelectionChanged() {
         newButton.setEnabled(true);
         Table builderTable= viewer.getTable();
         TableItem[] items = builderTable.getSelection();
         boolean validSelection= items != null && items.length > 0;
         boolean enableEdit= validSelection;
         boolean enableRemove= validSelection;
         boolean enableUp= validSelection;
         boolean enableDown= validSelection;
         if (validSelection) {
             if (items.length > 1) {
                 enableEdit= false;
             }
             int indices[]= builderTable.getSelectionIndices();
             int max = builderTable.getItemCount();
             enableUp= indices[0] != 0;
             enableDown= indices[indices.length - 1] < max - 1;
             for (int i = 0; i < items.length; i++) {
                 TableItem item = items[i];
                 Object data= item.getData();
                 if (data instanceof ILaunchConfiguration) {
                     ILaunchConfiguration config= (ILaunchConfiguration)data;
                     String builderName= null;
                     try {
                         builderName = config.getAttribute(IExternalToolConstants.ATTR_DISABLED_BUILDER, (String )null);
                     } catch (CoreException e) {
                     }
                     if (builderName != null) {
                         //do not allow "wrapped" builders to be removed or edited if they are valid
 IExtension ext= Platform.getExtensionRegistry().getExtension(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_BUILDERS, builderName);
                         enableEdit= false;
                         enableRemove= ext == null;
                     }
                 } else {
                     if (data instanceof ErrorConfig) {
                         enableEdit= false;
                         continue;
                     }
                     ICommand command= (ICommand) data;
                     enableEdit= command.isConfigurable();
                     IExtension ext= Platform.getExtensionRegistry().getExtension(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_BUILDERS, command.getBuilderName());
                     enableRemove= ext == null;
                     break;
                 }
             }
         }
         editButton.setEnabled(enableEdit);
         removeButton.setEnabled(enableRemove);
         upButton.setEnabled(enableUp);
         downButton.setEnabled(enableDown);
     }
     
     /**
      * Returns whether the given element (command or launch config)
      * is enabled.
      *
      * @param element the element
      * @return whether the given element is enabled
      */
     private boolean isEnabled(Object element) {
         if (element instanceof ICommand) {
             Boolean enabled= (Boolean )((ICommand) element).getArguments().get(COMMAND_ENABLED);
             if (enabled != null) {
                 return enabled.booleanValue();
             }
         } else if (element instanceof ILaunchConfiguration) {
             try {
                 return ExternalToolsUtil.isBuilderEnabled((ILaunchConfiguration) element);
             } catch (CoreException e) {
             }
         } else if (element instanceof ErrorConfig) {
             return false;
         }
         return true;
     }

     /**
      * Moves an entry in the builder table to the given index.
      */
     private void move(TableItem item, int index) {
         userHasMadeChanges= true;
         Object data = item.getData();
         item.dispose();
         viewer.insert(data, index);
         viewer.setChecked(data, isEnabled(data));
     }

     /**
      * Move the current selection in the build list down.
      */
     private void moveSelectionDown() {
         Table builderTable= viewer.getTable();
         int indices[]= builderTable.getSelectionIndices();
         if (indices.length < 1) {
             return;
         }
         int newSelection[]= new int[indices.length];
         int max= builderTable.getItemCount() - 1;
         for (int i = indices.length - 1; i >= 0; i--) {
             int index= indices[i];
             if (index < max) {
                 move (builderTable.getItem(index), index + 1);
                 newSelection[i]= index + 1;
             }
         }
         builderTable.setSelection(newSelection);
     }

     /**
      * Move the current selection in the build list up.
      */
     private void moveSelectionUp() {
         Table builderTable= viewer.getTable();
         int indices[]= builderTable.getSelectionIndices();
         int newSelection[]= new int[indices.length];
         for (int i = 0; i < indices.length; i++) {
             int index= indices[i];
             if (index > 0) {
                 move (builderTable.getItem(index), index - 1);
                 newSelection[i]= index - 1;
             }
         }
         builderTable.setSelection(newSelection);
     }

     /* (non-Javadoc)
      * @see org.eclipse.jface.preference.IPreferencePage#performOk()
      */
     public boolean performOk() {
         if (!userHasMadeChanges) {
             return super.performOk();
         }
         userHasMadeChanges= false;
         Table builderTable= viewer.getTable();
         int numCommands = builderTable.getItemCount();
         final Object [] itemData= new Object [numCommands];
         for (int i = 0; i < numCommands; i++) {
             itemData[i]= builderTable.getItem(i).getData();
         }
         IRunnableWithProgress runnable= new IRunnableWithProgress() {
             public void run(IProgressMonitor monitor)
                     throws InvocationTargetException , InterruptedException {
                 doPerformOk(monitor, itemData);
                 if (monitor.isCanceled()) {
                     throw new InterruptedException ();
                 }
             }
         };
         
         IProgressService service= PlatformUI.getWorkbench().getProgressService();
         try {
             service.busyCursorWhile(runnable);
         } catch (InvocationTargetException e) {
             return false;
         } catch (InterruptedException e) {
             return false;
         }
         return super.performOk();
     }
     
     private void doPerformOk(IProgressMonitor monitor, Object [] itemData) {
         if (monitor.isCanceled()) {
             return;
         }
         
         IProject project = getInputProject();
         //get all the build commands
 int numCommands = itemData.length;
         monitor.beginTask(ExternalToolsUIMessages.BuilderPropertyPage_3, numCommands + 1);
         List possibleCommands= new ArrayList (numCommands);
         for (int i = 0; i < numCommands; i++) {
             Object data = itemData[i];
             if (data instanceof ICommand) {
                 if (commandsToBeDeleted != null && commandsToBeDeleted.contains(data)) {
                     //command specified to be removed
 data= null;
                 }
                 ICommand command= (ICommand)data;
                 Map args= command.getArguments();
                 Boolean enabled= (Boolean )args.get(COMMAND_ENABLED);
                 if (enabled != null && enabled.equals(Boolean.FALSE)) {
                     ILaunchConfiguration config= disableCommand(command);
                     if (config != null) {
                         data= BuilderUtils.commandFromLaunchConfig(project,config);
                     }
                 } else {
                     args.remove(COMMAND_ENABLED);
                     command.setArguments(args);
                 }
             } else if (data instanceof ILaunchConfiguration) {
                 ILaunchConfiguration config= (ILaunchConfiguration) data;
                 String disabledBuilderName;
                 try {
                     disabledBuilderName = config.getAttribute(IExternalToolConstants.ATTR_DISABLED_BUILDER, (String )null);
                     if (disabledBuilderName != null && ExternalToolsUtil.isBuilderEnabled(config)) {
                         possibleCommands.add(translateBackToCommand(config, project));
                         continue;
                     }
                 } catch (CoreException e1) {
                 }
                 
                 if (!BuilderUtils.isUnmigratedConfig(config) && (config instanceof ILaunchConfigurationWorkingCopy)) {
                     ILaunchConfigurationWorkingCopy workingCopy= ((ILaunchConfigurationWorkingCopy) config);
                     // Save any changes to the config (such as enable/disable)
 if (workingCopy.isDirty()) {
                         try {
                             workingCopy.doSave();
                         } catch (CoreException e) {
                             Shell shell= getShell();
                             if (shell != null) {
                                 MessageDialog.openError(shell, ExternalToolsUIMessages.BuilderPropertyPage_39, NLS.bind(ExternalToolsUIMessages.BuilderPropertyPage_40, new String [] {workingCopy.getName()}));
                             }
                         }
                     }
                 }
                 data= BuilderUtils.commandFromLaunchConfig(project, config);
             } else if (data instanceof ErrorConfig) {
                 data= ((ErrorConfig) data).getCommand();
             }
             if (data != null) {
                 possibleCommands.add(data);
             }
             monitor.worked(1);
         }
        ICommand[] commands= new ICommand[possibleCommands.size()];
        possibleCommands.toArray(commands);
        if (checkCommandsForChange(commands)) {
            //set the build spec
 try {
                IProjectDescription desc = project.getDescription();
                desc.setBuildSpec(commands);
                project.setDescription(desc, IResource.FORCE, monitor);
            } catch (CoreException e) {
                handleException(e);
                performCancel();
            }
        }
        
        if (configsToBeDeleted != null) {
            deleteConfigurations();
        }
        monitor.done();
    }
    
    private void checkBuilderFolder() {
        try {
            IFolder builderFolder= BuilderUtils.getBuilderFolder(getInputProject(), false);
            if (builderFolder != null && builderFolder.exists() && builderFolder.members().length == 0) {
                // All files in the builder folder have been deleted. Clean up
 builderFolder.delete(true, false, null);
            }
        } catch (CoreException e) {
            handleException(e);
        }
    }

    /**
     * A non-external tool builder builder was disabled.
     * It has been re-enabled. Translate the disabled external tool builder launch configuration
     * wrapper back into the full fledged builder command.
     */
    private ICommand translateBackToCommand(ILaunchConfiguration config, IProject project) {
        try {
            ICommand newCommand = project.getDescription().newCommand();
            String builderName= config.getAttribute(IExternalToolConstants.ATTR_DISABLED_BUILDER, (String )null);
            Map args= config.getAttribute(IExternalToolConstants.ATTR_TOOL_ARGUMENTS, new HashMap (0));
            
            newCommand.setBuilderName(builderName);
            newCommand.setArguments(args);
            if (configsToBeDeleted == null) {
                configsToBeDeleted= new ArrayList ();
            }
            configsToBeDeleted.add(config);
            return newCommand;
        } catch (CoreException exception) {
            Shell shell= getShell();
            if (shell != null) {
                MessageDialog.openError(shell, ExternalToolsUIMessages.BuilderPropertyPage_13, ExternalToolsUIMessages.BuilderPropertyPage_error);
            }
            return null;
        }
    }
    
    /**
     * Disables a builder by wrappering the builder command as a disabled external tool builder.
     * The details of the command is persisted in the launch configuration.
     */
    private ILaunchConfiguration disableCommand(ICommand command) {
        Map arguments= command.getArguments();
        if (arguments != null) {
            arguments.remove(COMMAND_ENABLED);
        }
        List externalToolTypes= getConfigurationTypes(IExternalToolConstants.ID_EXTERNAL_TOOLS_BUILDER_LAUNCH_CATEGORY);
        if (externalToolTypes.size() == 0) {
            return null;
        }
        ILaunchConfigurationType type= (ILaunchConfigurationType)externalToolTypes.get(0);
        if (type == null) {
            return null;
        }
        boolean wasAutobuilding= ResourcesPlugin.getWorkspace().getDescription().isAutoBuilding();
        try {
            ILaunchConfigurationWorkingCopy workingCopy = null;
            String builderName = command.getBuilderName();
            String name= DebugPlugin.getDefault().getLaunchManager().generateUniqueLaunchConfigurationNameFrom(builderName);
            workingCopy = type.newInstance(BuilderUtils.getBuilderFolder(getInputProject(), true), name);
                    
            workingCopy.setAttribute(IExternalToolConstants.ATTR_DISABLED_BUILDER, builderName);
            if (arguments != null) {
                workingCopy.setAttribute(IExternalToolConstants.ATTR_TOOL_ARGUMENTS, arguments);
            }
            workingCopy.setAttribute(IExternalToolConstants.ATTR_BUILDER_ENABLED, false);
            ILaunchConfiguration config = null;
            setAutobuild(false);
            config = workingCopy.doSave();
            return config;
        } catch (CoreException e) {
            handleException(e);
        } finally {
            try {
                setAutobuild(wasAutobuilding);
            } catch (CoreException e) {
                handleException(e);
            }
        }
        return null;
    }

    private void deleteConfigurations() {
        boolean wasAutobuilding= ResourcesPlugin.getWorkspace().getDescription().isAutoBuilding();
        try {
            setAutobuild(false);
        
            Iterator itr= configsToBeDeleted.iterator();
            while (itr.hasNext()) {
                ILaunchConfiguration element = (ILaunchConfiguration) itr.next();
                element.delete();
            }
            
            checkBuilderFolder();
        } catch (CoreException e) {
            handleException(e);
        } finally {
            try {
                setAutobuild(wasAutobuilding);
            } catch (CoreException e) {
                handleException(e);
            }
        }
    }
    
    /**
     * Returns whether any of the commands have changed.
     */
    private boolean checkCommandsForChange(ICommand[] newCommands) {
        try {
            ICommand[] oldCommands = getInputProject().getDescription().getBuildSpec();
            if (oldCommands.length != newCommands.length) {
                return true;
            }
            IgnoreWhiteSpaceComparator comparator= new IgnoreWhiteSpaceComparator();
            for (int i = 0; i < oldCommands.length; i++) {
                ICommand oldCommand = oldCommands[i];
                ICommand newCommand= newCommands[i];
                String oldName= oldCommand.getBuilderName();
                String newName= newCommand.getBuilderName();
                if (oldName == null && newName != null) {
                    return true;
                }
                
                if(oldName != null && !oldName.equals(newName)) {
                    return true;
                }
                Map oldArgs= oldCommand.getArguments();
                Map newArgs= newCommand.getArguments();
                if (oldArgs == null && newArgs != null) {
                    return true;
                }
                if (oldArgs == null && newArgs == null) {
                    continue;
                }
                if(oldArgs.size() != newArgs.size()) {
                    return true;
                }
                Iterator keySet= oldArgs.keySet().iterator();
                while (keySet.hasNext()) {
                    Object key = keySet.next();
                    Object oldArg= oldArgs.get(key);
                    Object newArg= newArgs.get(key);
                    if (oldArg instanceof String && newArg instanceof String ) {
                        if (comparator.compare(oldArg, newArg) != 0) {
                            return true;
                        }
                    } else if (!oldArg.equals(newArg)){
                        return true;
                    }
                }
                
                if (oldCommand.isBuilding(IncrementalProjectBuilder.AUTO_BUILD) != newCommand.isBuilding(IncrementalProjectBuilder.AUTO_BUILD)
                        || oldCommand.isBuilding(IncrementalProjectBuilder.CLEAN_BUILD) != newCommand.isBuilding(IncrementalProjectBuilder.CLEAN_BUILD)
                        || oldCommand.isBuilding(IncrementalProjectBuilder.INCREMENTAL_BUILD) != newCommand.isBuilding(IncrementalProjectBuilder.INCREMENTAL_BUILD)
                        || oldCommand.isBuilding(IncrementalProjectBuilder.FULL_BUILD) != newCommand.isBuilding(IncrementalProjectBuilder.FULL_BUILD)) {
                    return true;
                }
            }
        } catch (CoreException ce) {
            return true;
        }
        return false;
    }

    /* (non-Javadoc)
     * @see org.eclipse.jface.preference.IPreferencePage#performCancel()
     */
    public boolean performCancel() {
        Iterator iter= newConfigList.iterator();
        while (iter.hasNext()) {
            try {
                ((ILaunchConfiguration) iter.next()).delete();
            } catch (CoreException e) {
                handleException(e);
            }
        }
        
        checkBuilderFolder();
        
        //remove the local marking of the enabled state of the commands
 Table builderTable= viewer.getTable();
        int numCommands = builderTable.getItemCount();
        for (int i = 0; i < numCommands; i++) {
            Object data = builderTable.getItem(i).getData();
            if (data instanceof ICommand) {
                ICommand command= (ICommand)data;
                Map args= command.getArguments();
                args.remove(COMMAND_ENABLED);
                command.setArguments(args);
            }
        }
        return super.performCancel();
    }
    
    public Shell getShell() {
        if (getControl().isDisposed()) {
            return null;
        }
        return super.getShell();
    }
}

